﻿

Public Class ZipMaker
    Implements IDisposable
    'Ctor :
    Public Sub New(ByVal zipnode As ZipNode, ByVal filters As List(Of Filter))
        Control.CheckForIllegalCrossThreadCalls = False
        _ZipNode = zipnode
        _Filters = filters
    End Sub
    'Dtor :
    Public Sub Dispose() Implements IDisposable.Dispose
        If _BackMaker.IsBusy Then
            _BackMaker.CancelAsync()
        End If
        If _BackSaver.IsBusy Then
            CancelZip()
        End If
        _ZipFile.Dispose()
    End Sub
    'Zip Node :
    Private _Filters As List(Of Filter)
    Private _ZipNode As ZipNode
    Public ReadOnly Property ZipNode As ZipNode
        Get
            Return _ZipNode
        End Get
    End Property
    'Zip Progress :
    Private _ZipTotalSize As Long
    Public ReadOnly Property ZipTotalSize As Long
        Get
            Return _ZipTotalSize
        End Get
    End Property
    Private _ZipWrittenSize As Long
    Public ReadOnly Property ZipWrittenSize As Long
        Get
            Return _ZipWrittenSize
        End Get
    End Property
    Public ReadOnly Property ZipPercent As Double
        Get
            Return CDbl(_ZipWrittenSize / _ZipTotalSize)
        End Get
    End Property

    'Entries Progress :
    Private _EntriesTotal As Long
    Public ReadOnly Property EntriesTotal As Long
        Get
            Return _EntriesTotal
        End Get
    End Property
    Private _EntriesWritten As Long
    Public ReadOnly Property EntriesWritten As Long
        Get
            Return _EntriesWritten
        End Get
    End Property

    'Current Entry Progress :
    Private _EntryTotalSize As Long
    Public ReadOnly Property EntryTotalSize As Long
        Get
            Return _EntryTotalSize
        End Get
    End Property
    Private _EntryWrittenSize As Long
    Public ReadOnly Property EntryWrittenSize As Long
        Get
            Return _EntryWrittenSize
        End Get
    End Property
    Public ReadOnly Property EntryPercent As Double
        Get
            Return CDbl(_EntryWrittenSize / _EntryTotalSize)
        End Get
    End Property

    'Timing Progress :
    Private _StartTime As TimeSpan
    Public ReadOnly Property StartTime As TimeSpan
        Get
            Return _StartTime
        End Get
    End Property
    Private _EllapsedTime As TimeSpan
    Public ReadOnly Property EllapsedTime As TimeSpan
        Get
            Return DateTime.Now.TimeOfDay - _StartTime
        End Get
    End Property
    Private _RemainingTime As TimeSpan
    Public ReadOnly Property RemainingTime As TimeSpan
        Get
            Return TimeSpan.FromSeconds((Me.ZipTotalSize - Me.ZipWrittenSize) / Me.ProgressSpeed)
        End Get
    End Property

    'Progress Speed :
    Private _ProgressSpeed As Double
    Public ReadOnly Property ProgressSpeed As Double
        Get
            Return CDbl(Me.ZipWrittenSize) / EllapsedTime.TotalSeconds
        End Get
    End Property
    'Reset Progress Fields :
    Private Sub ResetProgressFields()
        _ZipTotalSize = 0L
        _ZipWrittenSize = 0L
        _EntriesTotal = 0L
        _EntriesWritten = 0L
        _EntryTotalSize = 0L
        _EntryWrittenSize = 0L
        _LastEntryWrittenBytes = 0L
        _StartTime = TimeSpan.Zero
        _EllapsedTime = TimeSpan.Zero
        _RemainingTime = TimeSpan.Zero
        _ProgressSpeed = 0.0
    End Sub
    'Updating Bytes Written :
    Private _LastEntryWrittenBytes As Long
    Private Sub UpdateBytes(ByVal entrywrittenbytes As Long, ByRef entrysize As Long)
        _EntryWrittenSize = entrywrittenbytes
        _EntryTotalSize = entrysize
        _ZipWrittenSize += entrywrittenbytes - _LastEntryWrittenBytes
        If entrywrittenbytes = entrysize Then
            _EntriesWritten += 1
            _LastEntryWrittenBytes = 0
        Else
            _LastEntryWrittenBytes = entrywrittenbytes
        End If
    End Sub

    'Background Work :
    Private WithEvents _ZipFile As Zip.ZipFile
    Public ReadOnly Property ZipFile As Zip.ZipFile
        Get
            Return _ZipFile
        End Get
    End Property
    Private _IsWorking As Boolean = False
    Public ReadOnly Property IsWorking As Boolean
        Get
            Return _IsWorking
        End Get
    End Property
    Private WithEvents _BackMaker As BackgroundWorker
    Private WithEvents _BackSaver As BackgroundWorker

    'Background Making :
    Private _AddedSize As Long
    Private _AddedEntries As Long
    'Start Making Zip File :
    Public Function CanMakeZip() As Boolean
        If _ZipNode.InnerZipNodes.Count = 0 Then
            Return True
        Else
            Return False
        End If
    End Function
    Public Sub MakeZip()
        ResetProgressFields()
        _BackMaker = New BackgroundWorker With {.WorkerSupportsCancellation = True}
        _ZipFile = New Zip.ZipFile
        _ZipFile.ZipErrorAction = Zip.ZipErrorAction.InvokeErrorEvent
        _ZipNode.ZipSettings.ApplyToZip(_ZipFile)
        If _ZipNode.Type = ZipType.SplitZip Then
            _ZipNode.ZipSettings.Splitting.ApplyToZip(_ZipFile)
        End If
        _BackMaker.RunWorkerAsync()
    End Sub
    Private Sub _BackMaker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles _BackMaker.DoWork
        _IsWorking = True
        _ZipNode.Clean()
        Dim _EntriesFilesNodes As List(Of FileNode) = _ZipNode.InnerFilesNode
        _ZipTotalSize = _ZipNode.Size
        _EntriesTotal = _EntriesFilesNodes.Count
        _AddedSize = 0L
        _AddedEntries = 0L
        RaiseEvent ZipMakeStarted(Me, New ZipMakingEventArgs(_AddedSize, _ZipTotalSize, _AddedEntries, _EntriesTotal))
        For Each fn As FileNode In _EntriesFilesNodes
            _AddedEntries += 1
            _AddedSize += fn.Size
            AddHandler _ZipFile.AddProgress, AddressOf _Zip_AddProgress
            Dim ze As Zip.ZipEntry = _ZipFile.AddFile(fn.SourcePath, fn.ZipPath)
            fn.EntrySettings.ApplyToZipEntry(ze)
            For Each fil As Filter In _Filters
                fil.ApplyToZipEntry(ze)
            Next
        Next
    End Sub
    'Adding progress Event :
    Private Sub _Zip_AddProgress(sender As Object, e As Ionic.Zip.AddProgressEventArgs)
        If e.EventType = Zip.ZipProgressEventType.Adding_AfterAddEntry Then
            RaiseEvent ZipMakeUpdated(Me, New ZipMakingEventArgs(_AddedSize, _ZipTotalSize, _AddedEntries, _EntriesTotal))
            RemoveHandler _ZipFile.AddProgress, AddressOf _Zip_AddProgress
        End If
    End Sub
    'Adding Finished :
    Private Sub _BackMaker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles _BackMaker.RunWorkerCompleted
        RaiseEvent ZipMakeFinished(Me, New ZipMakingEventArgs(_AddedSize, _ZipTotalSize, _AddedEntries, _EntriesTotal))
        _BackMaker.Dispose()
        _BackMaker = Nothing
    End Sub
    Public Event ZipMakeStarted As EventHandler(Of ZipMakingEventArgs)
    Public Event ZipMakeUpdated As EventHandler(Of ZipMakingEventArgs)
    Public Event ZipMakeFinished As EventHandler(Of ZipMakingEventArgs)
    'Background Saving :
    Private _ZipPath As String
    Private _ZipLocation As String
    Private _CancelSaving As Boolean = False
    Private _ZipSavingError As Exception
    Private _Resultfilesnodes As List(Of FileNode)
    'Save Zip :
    Public Sub SaveZip(ByVal location As String)
        ResetProgressFields()
        _ZipTotalSize = _ZipNode.Size
        _EntriesTotal = _ZipFile.Count
        _CancelSaving = False
        _ZipSavingError = Nothing
        _ZipFile.TempFileFolder = My.Computer.FileSystem.SpecialDirectories.Temp
        _ZipFile.ZipErrorAction = Zip.ZipErrorAction.Throw
        _ZipLocation = location
        _ZipPath = IO.Path.Combine(_ZipLocation, _ZipNode.Text & _ZipNode.Type.Extension)
        _BackSaver = New BackgroundWorker With {.WorkerSupportsCancellation = True}
        _BackSaver.RunWorkerAsync()
    End Sub
    'Cancel Zip :
    Public Sub CancelZip()
        If _CancelSaving = False Then
            _CancelSaving = True
        End If
    End Sub
    'BackSaver :
    Private Sub _BackSaver_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles _BackSaver.DoWork
        If _BackSaver.CancellationPending = False Then
            _IsWorking = True
            Try
                If _ZipNode.Type = ZipType.SfxZip Then
                    Dim saveopts As Zip.SelfExtractorSaveOptions = _ZipNode.ZipSettings.SfxOptions.SfxSaveOptions
                    _ZipFile.SaveSelfExtractor(_ZipPath, saveopts)
                Else
                    _ZipFile.Save(_ZipPath)
                End If
            Catch ex As Exception
                _ZipSavingError = ex
                Me.CancelZip()
            End Try
        Else
            e.Cancel = True
        End If
    End Sub
    Private Sub _BackSaver_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles _BackSaver.RunWorkerCompleted
        If _CancelSaving = True Then
            If _ZipSavingError Is Nothing Then
                ResetProgressFields()
                _ZipTotalSize = _ZipNode.Size
                _EntriesTotal = _ZipFile.Count
                RaiseEvent ZipSavingCanceled(Me, Nothing)
            Else
                RaiseEvent ZipSavingErrorOccured(Me, New ZipSavingErrorOccuredEventArgs(_ZipSavingError))
            End If
        Else
            _Resultfilesnodes = New List(Of FileNode)
            Dim ext As String = _ZipNode.Type.Extension
            If _ZipNode.Type = ZipType.SfxZip OrElse _ZipNode.Type = ZipType.SimpleZip Then
                _Resultfilesnodes.Add(New FileNode(_ZipPath))
            Else
                Dim di As New IO.DirectoryInfo(_ZipLocation)
                For Each fi As IO.FileInfo In di.EnumerateFiles
                    If IO.Path.GetFileNameWithoutExtension(fi.FullName) = _ZipNode.Text AndAlso IO.Path.GetExtension(fi.FullName).StartsWith(".z") = True Then
                        _Resultfilesnodes.Add(New FileNode(fi.FullName))
                    End If
                Next
            End If
            RaiseEvent ZipSavingFinished(Me, New ZipSavingFinishedEventArgs(_ZipNode, _Resultfilesnodes))
        End If
        _BackSaver.Dispose()
        _BackSaver = Nothing
        _IsWorking = False
    End Sub
    'Saving Progress Event :
    Private _Update As Boolean = True
    Private _ZipEntry As String = ""
    Private Sub _Zip_SaveProgress(sender As Object, e As Ionic.Zip.SaveProgressEventArgs) Handles _ZipFile.SaveProgress
        If _CancelSaving = False Then
            Select Case e.EventType
                Case Zip.ZipProgressEventType.Saving_Started
                    _StartTime = DateTime.Now.TimeOfDay
                    RaiseEvent ZipSavingStarted(Me, Nothing)
                Case Zip.ZipProgressEventType.Saving_BeforeWriteEntry
                    RaiseEvent ZipEntryReady(Me, New ZipEntryReadyEventArgs(e.CurrentEntry.FileName))
                Case Zip.ZipProgressEventType.Saving_EntryBytesRead
                    Me.UpdateBytes(e.BytesTransferred, e.TotalBytesToTransfer)
                    RaiseEvent ZipSavingUpdated(Me, Nothing)
                Case Zip.ZipProgressEventType.Error_Saving
                    _ZipSavingError = New IO.IOException("In / Out Error Occured When Saving Zip File")
                    Me.CancelZip()
            End Select
        Else
            e.Cancel = True
            _BackSaver.CancelAsync()
        End If
    End Sub
    'Saving Zip Error :
    Private Sub _Zip_ZipError(sender As Object, e As Ionic.Zip.ZipErrorEventArgs) Handles _ZipFile.ZipError
        If e.EventType = Zip.ZipProgressEventType.Error_Saving Then
            _ZipSavingError = e.Exception
            Me.CancelZip()
        End If
    End Sub
    Public Event ZipSavingStarted As EventHandler(Of EventArgs)
    Public Event ZipSavingUpdated As EventHandler(Of EventArgs)
    Public Event ZipEntryReady As EventHandler(Of ZipEntryReadyEventArgs)
    Public Event ZipSavingCanceled As EventHandler(Of EventArgs)
    Public Event ZipSavingErrorOccured As EventHandler(Of ZipSavingErrorOccuredEventArgs)
    Public Event ZipSavingFinished As EventHandler(Of ZipSavingFinishedEventArgs)
End Class